package cz.muni.fi.pb138.accountingRecords;

import java.io.ByteArrayOutputStream;
import java.math.BigDecimal;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;
import org.exist.xmldb.EXistResource;
import org.w3c.dom.*;
import org.xmldb.api.DatabaseManager;
import org.xmldb.api.base.Collection;
import org.xmldb.api.base.Database;
import org.xmldb.api.base.XMLDBException;
import org.xmldb.api.modules.XMLResource;

/**
 * Class implementing InvoiceManager interface, uses eXist database at port 8899
 * as database server, XMLSchema file 'invoice.xsd' and class XMLValidator for validation
 * @author Michal
 */
public class InvoiceManagerImpl implements InvoiceManager {

    private static String URI = "xmldb:exist://localhost:8899/exist/xmlrpc";
    private static final String driver = "org.exist.xmldb.DatabaseImpl";
    private String collectionName;
    private String login;
    private String password;
    Database database;
    private static final Logger logger = Logger.getLogger(InvoiceManagerImpl.class.getName());

    /**
     * Creates InvoiceManagerImpl object and sets credentials for database connection
     * @param collectionName name of collection in database
     * @param login user name for connection to database
     * @param password password for connection to database
     * @throws IllegalArgumentException if any passed parameter is null
     * @throws ServiceFailureException if exception occured during registering
     * database, e.g. wrong credentials, database server not found, wrong libraries etc.
     */
    public InvoiceManagerImpl(String collectionName, String login, String password) {
        try {
            if(collectionName == null){
                throw new IllegalArgumentException("collectionName is null");
            }
            if(login == null){
                throw new IllegalArgumentException("login is null");
            }
            if(password == null){
                throw new IllegalArgumentException("password is null");
            }
            this.collectionName = collectionName;
            this.login = login;
            this.password = password;

            Class cl = Class.forName(driver);
            database = (Database) cl.newInstance();
            database.setProperty("create-database", "true");
            DatabaseManager.registerDatabase(database);

        } catch (Exception ex) {
            logger.log(Level.SEVERE, "Unable to establish DB connection",ex);
            throw new ServiceFailureException("Unable to establish DB connection",ex);
        }
    }
    /**
     * Adds new invoice to database
     * @param invoice invoice that will be added to DB
     * @throws IllegalArgumentException if any attribute is null or has wrong format
     * @throws ServiceFailureException on any service failure
     */
    public void addInvoice(Invoice invoice) {
        String id;
        if (invoice == null) {
            throw new IllegalArgumentException("Invoice is null");
        }
        if (invoice.getId() != null) {
            logger.log(Level.SEVERE, "tried to add invoice with id: {0}, that is already in DB",
                    invoice.getId());
            throw new IllegalArgumentException("Invoice is already in database");
        }
        //validate invoice object
        validateInvoice(invoice);
        Collection col = setAndCheckCollection();
        try {
            id = col.createId();
            invoice.setId(id);
        } catch (XMLDBException ex) {
            logger.log(Level.SEVERE, "creating id failed", ex);
            throw new ServiceFailureException("can't create ID", ex);
        }
        Document doc = createDOMFromInvoice(invoice);
        //validate resulting XML
        XMLValidator validator = new XMLValidator("invoice.xsd");
        validator.checkXML(doc);

        XMLResource res = null;
        try {
            res = (XMLResource) col.createResource(invoice.getId(), "XMLResource");
            res.setContentAsDOM(doc);
            col.storeResource(res);
            logger.log(Level.SEVERE,
                    "invoice added to database with ID: {0}", res.getId());
        } catch (XMLDBException ex) {
            invoice.setId(null);
            logger.log(Level.SEVERE,
                    "error occured during adding document with ID: {0} to database",
                    invoice.getId());
            throw new ServiceFailureException("Error occured during adding "
                    + "document to database", ex);
        } finally {
            cleanUpResources(res,col);
        }
    }
    /**
     * retrieves invoice with given ID from database
     * @param id id of invoice that will be retrieved
     * @return invoice with given id
     * @throws IllegalArgumentException if given ID is null, empty string or invoice
     * with given ID is not in DB
     * @throws ServiceFailureException on any service failure
     */
    public Invoice getInvoice(String id) {
        Document doc = getInvoiceAsDOM(id);
        Invoice invoice = createInvoiceFromDOM(doc, id);
            logger.log(Level.SEVERE, "invoice with ID: {0} retrieved", id);
            return invoice;
    }
    /**
     * retrieves invoice document with given ID from database
     * and returns it as HTML Document ready for print
     * @param id id of invoice what will be retrieved
     * @return string containing HTML document transformed from invoice with given id
     * @throws IllegalArgumentException if id is null, empty string or invoice with
     * given ID is not in database
     * @throws ServiceFailureException on any service failure
     */
    public String getInvoiceAsHTML(String id) {
        Document doc = getInvoiceAsDOM(id);
        String xslFile = null;
        try{
            xslFile = InvoiceManagerImpl.class.getResource("invoice.xsl").toString();
        }
        catch (NullPointerException ex){
            logger.log(Level.SEVERE, "XSLT style is missing", ex);
            throw new ServiceFailureException("XSLT style is missing", ex);
        }
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        TransformerFactory tFactory = TransformerFactory.newInstance();
        try {
            Transformer transformer = tFactory.newTransformer( 
                    new StreamSource(xslFile));
            transformer.transform(new DOMSource(doc), new StreamResult(baos));
        }
        catch (TransformerException ex) {
            logger.log(Level.SEVERE, "XSLT transformation caused exception", ex);
            throw new ServiceFailureException("XSLT transformation caused exception", ex);
        }
        String resultHTML = baos.toString();
        return resultHTML;
    }
    /**
     * updates invoice with given ID in database
     * @param invoice invoice containing attributes that will be stored in database
     * and ID of invoice, that will be replaced
     * @throws IllegalArgumentException if any attribute is null or has wrong format
     * or invoice with given ID is not in DB
     * @throws ServiceFailureException on any service failure
     */
    public void updateInvoice(Invoice invoice) {
        if (invoice == null) {
            throw new IllegalArgumentException("invoice is null");
        }
        if (invoice.getId() == null || invoice.getId().isEmpty()) {
            logger.log(Level.SEVERE, "attempt to update invoice with blank id");
            throw new IllegalArgumentException("id is null or empty, this resource "
                    + "can't be in DB");
        }
        validateInvoice(invoice);

        Collection col = setAndCheckCollection();
        XMLResource res = null;
        try {
            res = (XMLResource) col.getResource(invoice.getId());
            if (res == null) {
                logger.log(Level.SEVERE, "invoice with id: {0} is not in DB, can't be updated",
                        invoice.getId());
                throw new IllegalArgumentException("Invoice with id: " + invoice.getId()
                        + " is not in DB");
            }

            Document doc = createDOMFromInvoice(invoice);

            XMLValidator validator = new XMLValidator("invoice.xsd");
            validator.checkXML(doc);

            res.setContentAsDOM(doc);
            col.storeResource(res);
        } catch (XMLDBException ex) {
            logger.log(Level.SEVERE, "exception occured during update", ex);
            throw new ServiceFailureException("Exception occured during update", ex);
        } finally {
            cleanUpResources(res, col);
        }
    }
    /**
     * removes invoice with given ID from database
     * @param id id of invoice that will be deleted
     * @throws IllegalArgumentException if id is null, empty string or invoice with
     * given ID is not in DB
     * @throws ServiceFailureException on any service failure
     */
    public void removeInvoice(String id) {
        if (id == null || id.isEmpty()) {
            logger.log(Level.SEVERE, "attempt to remove invoice with blank id, can't be in DB");
            throw new IllegalArgumentException("id is null or empty");
        }

        Collection col;
        col = setAndCheckCollection();

        XMLResource res = null;
        try {
            res = (XMLResource) col.getResource(id);
            if (res == null) {
                logger.log(Level.SEVERE, "no resource with ID: {0} in DB", id);
                throw new IllegalArgumentException("No resource with id: " + id + " in DB");
            }
            col.removeResource(res);
            logger.log(Level.SEVERE, "invoice with id: {0} was removed", id);
        } catch (XMLDBException ex) {
            logger.log(Level.SEVERE, "exception occured during removing invoice with ID: {0}", id);
            throw new ServiceFailureException("Error while removing invoice from DB", ex);
        } finally {
            cleanUpResources(res, col);
        }
    }
    /**
     * retrieves from database all invoices with due date from given period
     * @param from start of period
     * @param to end of period
     * @return list of invoices for given period
     * @throws IllegalArgumentException if from date or to date is null or to date
     * is before from date
     * @throws ServiceFailureException on any service failure
     */
    public List<Invoice> getInvoicesForPeriod(Date from, Date to) {
        if(from == null){
            throw new IllegalArgumentException("from is null");
        }
        if(to == null){
            throw new IllegalArgumentException("to is null");
        }
        if(from.after(to)){
            throw new IllegalArgumentException("to date is before from date");
        }
        Collection col = setAndCheckCollection();

        List<Invoice> invoiceList = new ArrayList<Invoice>();
        String[] idList;
        try {
            idList = col.listResources();
        } catch (XMLDBException ex) {
            Logger.getLogger(InvoiceManagerImpl.class.getName()).log(Level.SEVERE,
                    "getting IDs of all resources failed", ex);
            throw new ServiceFailureException("getting IDs of all resources failed",ex);
        }
        for(int i = 0; i < idList.length; i++){
            Invoice invoice = getInvoice(idList[i]);
            if(invoice.getDueDate().after(from) && invoice.getDueDate().before(to)){
                invoiceList.add(invoice);
            }
        }
        logger.log(Level.SEVERE, "retrieved invoices for period from: {0} to: {1}",
                new Object[]{from, to});
        return invoiceList;
    }
    /**
     * returns sum for all items in all invoices in given period
     * @param from start  of period
     * @param to end of period
     * @return sum for prices of all items in all invoices for given period
     * @throws IllegalArgumentException if from date or to date is null or to date
     * is before from date
     * @throws ServiceFailureException on any service failure
     */
    public BigDecimal sumOfInvoicesForPeriod(Date from, Date to) {
        List<Invoice> invoiceList = getInvoicesForPeriod(from, to);
        BigDecimal sum = new BigDecimal(0);
        for(int i = 0; i < invoiceList.size(); i++){
            for(int j = 0; j < invoiceList.get(i).getItems().size(); j++){
                sum = sum.add(invoiceList.get(i).getItems().get(j).getPriceForUnit()
                        .multiply(new BigDecimal(invoiceList.get(i)
                        .getItems().get(j).getCount())));
            }
    }
        logger.log(Level.SEVERE, "retrieved sum of invoices from period from: {0} to: {1}",
                new Object[]{from, to});
        return sum;
    }
    /*
     * transform Invoice object to DOM document
     */
    private Document createDOMFromInvoice(Invoice invoice) {
        Document doc = null;
        try {
            DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance();
            DocumentBuilder docBuilder = docFactory.newDocumentBuilder();

            doc = docBuilder.newDocument();
            //invoice
            Element invoiceElement = doc.createElement("invoice");
            doc.appendChild(invoiceElement);
            //invoice_no
            Element invoiceNumberElement = doc.createElement("invoice_no");
            invoiceNumberElement.setTextContent(invoice.getInvoiceNumber().toString());
            invoiceElement.appendChild(invoiceNumberElement);
            //distributor
            Element distributorElement = doc.createElement("distributor");
            createSubjectElement(invoice.getDistributor(),
                    doc, distributorElement);
            invoiceElement.appendChild(distributorElement);
            //consumer
            Element consumerElement = doc.createElement("consumer");
            createSubjectElement(invoice.getConsumer(),
                    doc, consumerElement);
            invoiceElement.appendChild(consumerElement);
            //items
            Element itemsElement = doc.createElement("items");
            for (int i = 0; i < invoice.getItems().size(); i++) {
                Element itemElement = doc.createElement("item");
                Element itemNoteElement = doc.createElement("note");
                itemNoteElement.setTextContent(invoice.getItems().get(i).getNote());
                itemElement.appendChild(itemNoteElement);
                Element itemCountElement = doc.createElement("count");
                itemCountElement.setTextContent(String.valueOf(invoice.getItems().get(i).getCount()));
                itemElement.appendChild(itemCountElement);
                Element itemPriceForUnitElement = doc.createElement("price_for_unit");
                itemPriceForUnitElement.setTextContent(invoice.getItems()
                        .get(i).getPriceForUnit().toString());
                itemElement.appendChild(itemPriceForUnitElement);
                itemsElement.appendChild(itemElement);
            }
            invoiceElement.appendChild(itemsElement);
            //payment_type
            Element paymentTypeElement = doc.createElement("payment_type");
            paymentTypeElement.setTextContent(invoice.getPaymentType().toString());
            invoiceElement.appendChild(paymentTypeElement);
            //invoicing_date
            Element invoicingDateElement = doc.createElement("invoicing_date");
            SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
            invoicingDateElement.setTextContent(format.format(invoice.getInvoicingDate()));
            invoiceElement.appendChild(invoicingDateElement);
            //due_date
            Element dueDateElement = doc.createElement("due_date");
            dueDateElement.setTextContent(format.format(invoice.getDueDate()));
            invoiceElement.appendChild(dueDateElement);
            //bank_account  -- implied
            if(invoice.getBankAccount() != null) {
                Element bankAccountElement = doc.createElement("bank_account");
                bankAccountElement.setTextContent(invoice.getBankAccount());
                invoiceElement.appendChild(bankAccountElement);
            }
            //variable_symbol --implied
            if(invoice.getVariableSymbol() != null){
                Element variableSymbolElement = doc.createElement("variable_symbol");
                variableSymbolElement.setTextContent(String.valueOf(invoice.getVariableSymbol()));
                invoiceElement.appendChild(variableSymbolElement);
            }
            //drawer
            Element drawerElement = doc.createElement("drawer");
            drawerElement.setTextContent(invoice.getDrawer());
            invoiceElement.appendChild(drawerElement);
            return doc;

        } catch (ParserConfigurationException ex) {
            Logger.getLogger(InvoiceManagerImpl.class.getName()).log(Level.SEVERE,
                    "error occured during parsing of XML", ex);
            throw new ServiceFailureException("Error occured during parsing of xml", ex);
        }
    }
    /*
     * creates DOM Element for given subject and appends it to parent element
     */
    private void createSubjectElement(Subject subject, Document doc, Element parentElement) {
        //subject_name
        Element subjectNameElement = doc.createElement("subject_name");
        subjectNameElement.setTextContent(subject.getSubjectName());
        parentElement.appendChild(subjectNameElement);
        //street --implied
        if (subject.getStreet() != null) {
            Element streetElement = doc.createElement("street");
            streetElement.setTextContent(subject.getStreet());
            parentElement.appendChild(streetElement);
        }
        //number
        Element numberElement = doc.createElement("number");
        numberElement.setTextContent(String.valueOf(subject.getNumber()));
        parentElement.appendChild(numberElement);
        //city
        Element cityElement = doc.createElement("city");
        cityElement.setTextContent(subject.getCity());
        parentElement.appendChild(cityElement);
        //postal_code
        Element postalCodeElement = doc.createElement("postal_code");
        postalCodeElement.setTextContent(String.valueOf(subject.getPostalCode()));
        parentElement.appendChild(postalCodeElement);
        //phone --implied
        if (subject.getPhone() != null) {
            Element phoneElement = doc.createElement("phone");
            phoneElement.setTextContent(subject.getPhone());
            parentElement.appendChild(phoneElement);
        }
        //mail --implied
        if (subject.getMail() != null) {
            Element mailElement = doc.createElement("mail");
            mailElement.setTextContent(subject.getMail());
            parentElement.appendChild(mailElement);
        }
        //id_number --implied
        if (subject.getIdNumber() != null) {
            Element idNumberElement = doc.createElement("id_number");
            idNumberElement.setTextContent(subject.getIdNumber().toString());
            parentElement.appendChild(idNumberElement);
        }
    }
    /*
     * validates Invoice object so it meets limitations given by xml schema
     */
    private void validateInvoice(Invoice invoice) {
        if (invoice == null) {
            throw new IllegalArgumentException("invoice is null");
        }
        if (invoice.getBankAccount() != null && !invoice.getBankAccount().matches("[1-9][0-9]{8}/[0-9]{4}")) {
            throw new IllegalArgumentException("bank account has wrong format");
        }
        if (invoice.getConsumer() == null) {
            throw new IllegalArgumentException("consumer is null");
        }
        validateSubject(invoice.getConsumer());
        if (invoice.getDistributor() == null) {
            throw new IllegalArgumentException("distributor is null");
        }
        validateSubject(invoice.getDistributor());
        if (invoice.getDrawer() == null || invoice.getDrawer().isEmpty()) {
            throw new IllegalArgumentException("drawer is null or empty string");
        }
        if (invoice.getDueDate() == null) {
            throw new IllegalArgumentException("due date is null");
        }
        if (invoice.getInvoicingDate() == null) {
            throw new IllegalArgumentException("invoicing date is null");
        }
        if (invoice.getInvoiceNumber() == null) {
            throw new IllegalArgumentException("invoice number is null");
        }
        validateItems(invoice.getItems());
        if (invoice.getPaymentType() == null) {
            throw new IllegalArgumentException("paymentType is null");
        }
        if (invoice.getVariableSymbol() != null && (invoice.getVariableSymbol() < 0
                || invoice.getVariableSymbol() > 9999999999l)) {
            throw new IllegalArgumentException("variable symbol must be 1-10 digits long");
        }
    }
    /*
     * validates Subject object so it meets limitations given by xml schema
     */
    private void validateSubject(Subject subject) {
        if (subject == null) {
            throw new IllegalArgumentException("subject is null");
        }
        if (subject.getStreet() != null && subject.getStreet().isEmpty()){
            throw new IllegalArgumentException("street is empty string");
        }
        if (subject.getNumber() == null){
            throw new IllegalArgumentException("number is null");
        }
        if (subject.getNumber() <= 0){
            throw new IllegalArgumentException("street number must be positive");
        }
        if (subject.getCity() == null|| subject.getCity().isEmpty()) {
            throw new IllegalArgumentException("city is null or empty string");
        }
        if (subject.getIdNumber() != null && (subject.getIdNumber() < 10000000
                || subject.getIdNumber() > 99999999)) {
            throw new IllegalArgumentException("ID number must be between 10000000 and 99999999");
        }
        if (subject.getMail() != null && !subject.getMail()
                .matches(".+@.+")) {
            throw new IllegalArgumentException("wrong format of mail");
        }
        if (subject.getPhone() != null && !subject.getPhone().matches("\\+[1-9][0-9]{11}")) {
            throw new IllegalArgumentException("wrong format of phone");
        }

        if(subject.getPostalCode() == null){
            throw new IllegalArgumentException("postal code is null");
        }

        if (subject.getPostalCode() < 10000 || subject.getPostalCode() >= 80000) {
            throw new IllegalArgumentException("wrong postal code, must be between"
                    + " 10000 inclusive and 80000 exclusive");
        }

        if (subject.getSubjectName() == null) {
            throw new IllegalArgumentException("subject name is null");
        }
    }
    /*
     * validates items array so it meets limitations given by xml schema
     */
    private void validateItems(List<Item> items) {
        if (items == null) {
            throw new IllegalArgumentException("items is null");
        }
        for (int i = 0; i < items.size(); i++) {
            validateItem(items.get(i));
        }
    }
    /*
     * validates one item so it meets limitations given by xml schema
     */
    private void validateItem(Item item) {
        if (item == null) {
            throw new IllegalArgumentException("item is null");
        }
        if (item.getNote() == null || item.getNote().isEmpty()) {
            throw new IllegalArgumentException("note is null or empty string");
        }
        if(item.getCount() == null){
            throw new IllegalArgumentException("count is null");
        }
        if (item.getCount() < 1) {
            throw new IllegalArgumentException("count is less than 1");
        }
        if(item.getPriceForUnit() == null){
            throw new IllegalArgumentException("price for unit is null");
        }
        if (item.getPriceForUnit().doubleValue() <= 0) {
            throw new IllegalArgumentException("price for unit must be positive");
        }
    }
    /*
     * creates Invoice object from DOM Document
     */
    private Invoice createInvoiceFromDOM(Document doc, String id) {
        if (doc == null) {
            throw new IllegalArgumentException("doc is null");
        }
        XMLValidator validator = new XMLValidator("invoice.xsd");
        validator.checkXML(doc);
        Invoice invoice = new Invoice();
        //id
        invoice.setId(id);
        //invoiceNumber
        invoice.setInvoiceNumber(Long.parseLong(doc.getElementsByTagName("invoice_no")
                .item(0).getTextContent()));
        //distributor
        Subject distributor = new Subject();
        invoice.setDistributor(distributor);
        Element distributorElement = (Element) doc.getElementsByTagName("distributor").item(0);
        createSubjectFromElement(distributor, distributorElement);
        //consumer
        Subject consumer = new Subject();
        invoice.setConsumer(consumer);
        Element consumerElement = (Element) doc.getElementsByTagName("consumer").item(0);
        createSubjectFromElement(consumer, consumerElement);
        //items
        Element itemsElement = (Element) doc.getElementsByTagName("items").item(0);
        List<Item> itemList = new ArrayList<Item>();
        for(int i = 0; i < itemsElement.getElementsByTagName("item").getLength();i++){
            Element itemElement = (Element) itemsElement.getElementsByTagName("item").item(i);
            Item item = new Item();
            item.setNote(itemElement.getElementsByTagName("note").item(0).getTextContent());
            item.setCount(Integer.parseInt(itemElement.getElementsByTagName("count")
                    .item(0).getTextContent()));
            item.setPriceForUnit(new BigDecimal(itemElement
                    .getElementsByTagName("price_for_unit").item(0).getTextContent()));
            itemList.add(item);
        }
        invoice.setItems(itemList);
        //paymentType
        String paymentTypeString = doc.getElementsByTagName("payment_type").item(0)
                .getTextContent();
        if("cash".equals(paymentTypeString.toLowerCase())){
            invoice.setPaymentType(PaymentType.CASH);
        }
        else if("creditcard".equals(paymentTypeString.toLowerCase()))
        {
            invoice.setPaymentType(PaymentType.CREDITCARD);
        }
        else if ("transaction".equals(paymentTypeString.toLowerCase())){
            invoice.setPaymentType(PaymentType.TRANSACTION);
        }
        else {
            throw new ServiceFailureException("illegal payment type: " + paymentTypeString);
        }
        SimpleDateFormat format = new SimpleDateFormat ("yyyy-MM-dd'T'HH:mm:ss");
        try {
            //invoicingDate
            invoice.setInvoicingDate(format.parse(doc.getElementsByTagName("invoicing_date")
                    .item(0).getTextContent()));
            //dueDate
            invoice.setDueDate(format.parse(doc.getElementsByTagName("due_date")
                    .item(0).getTextContent()));
        } catch (ParseException ex) {
            throw new ServiceFailureException("Wrong format of date",ex);
        }
        //bankAccount
        if (doc.getElementsByTagName("bank_account").getLength() == 0) {
           invoice.setBankAccount(null);
        } else {
            invoice.setBankAccount(doc.getElementsByTagName("bank_account")
                    .item(0).getTextContent());
        }
        //variableSymbol
        if (doc.getElementsByTagName("variable_symbol").getLength() == 0) {
           invoice.setVariableSymbol(null);
        } else {
            invoice.setVariableSymbol(Long.parseLong(doc.getElementsByTagName("variable_symbol")
                    .item(0).getTextContent()));
        }
        //drawer
        invoice.setDrawer(doc.getElementsByTagName("drawer").item(0).getTextContent());
        return invoice;
    }
    /*
     * creates Subject object from DOM Document
     */
    private void createSubjectFromElement(Subject subject, Element subjectElement) {
        //subjectName
        subject.setSubjectName(subjectElement.getElementsByTagName("subject_name")
                .item(0).getTextContent());
        //street --implied
        if (subjectElement.getElementsByTagName("street").getLength() == 0) {
            subject.setStreet(null);
        } else {
            subject.setStreet(subjectElement.getElementsByTagName("street").item(0).getTextContent());
        }
        //number
        subject.setNumber(Integer.parseInt(subjectElement.getElementsByTagName("number")
                .item(0).getTextContent()));
        //city
        subject.setCity(subjectElement.getElementsByTagName("city").item(0).getTextContent());
        //postalCode
        subject.setPostalCode(Integer.parseInt(subjectElement.getElementsByTagName("postal_code")
                .item(0).getTextContent()));
        //phone --implied
        if (subjectElement.getElementsByTagName("phone").getLength() == 0) {
            subject.setPhone(null);
        } else {
            subject.setPhone(subjectElement.getElementsByTagName("phone").item(0).getTextContent());
        }
        //mail --implied
        if (subjectElement.getElementsByTagName("mail").getLength() == 0) {
            subject.setMail(null);
        } else {
            subject.setMail(subjectElement.getElementsByTagName("mail").item(0).getTextContent());
        }
        //idNumber --implied
        if (subjectElement.getElementsByTagName("id_number").getLength() == 0) {
            subject.setIdNumber(null);
        } else {
            subject.setIdNumber(Integer.parseInt(subjectElement.getElementsByTagName("id_number")
                .item(0).getTextContent()));
        }
    }
    /*
     * retrieves invoice document with given ID from database
     * and returns it as XML Document
     */
    private Document getInvoiceAsDOM(String id) {
        XMLResource res = null;
        if (id == null || id.isEmpty()) {
            throw new IllegalArgumentException("id is null or empty string");
        }
        Collection col = setAndCheckCollection();
        try {
            res = (XMLResource) col.getResource(id);
            if (res == null) {
                logger.log(Level.SEVERE, "tried to retrieve invoice with id: {0} "
                        + "that was not in DB", id);
                throw new IllegalArgumentException("invoice with id: "+id+" is not in DB");
            }

            Document doc = (Document) res.getContentAsDOM();
            if (doc == null) {
                logger.log(Level.SEVERE, "retrieved DOM is null");
                throw new ServiceFailureException("retrieved DOM is null");
            }

            XMLValidator validator = new XMLValidator("invoice.xsd");
            validator.checkXML(doc);

            logger.log(Level.SEVERE, "invoice with ID: {0} retrieved as DOM", id);
            return doc;
        } catch (XMLDBException ex) {
            logger.log(Level.SEVERE, "exception occured while retrieving invoice from DB");
            throw new ServiceFailureException("Exception occured while retrieving"
                    + " invoice from DB", ex);
        } finally {
            cleanUpResources(res, col);
        }
    }
    /*
     * retrieves collection from database using DatabaseManager and checks if this
     * collection is not null
     */
    private Collection setAndCheckCollection() {
        Collection col = null;
        try {
            col = DatabaseManager.getCollection(URI + collectionName, login, password);
        } catch (XMLDBException ex) {
            logger.log(Level.SEVERE, null, ex);
            throw new ServiceFailureException("Can't establish connection to database");
        }
        if (col == null) {
            logger.log(Level.SEVERE, "collection was not found");
            throw new ServiceFailureException("collection was not found");
        }
        return col;
    }
    /*
     * frees resources and close connection to database
     */
    private void cleanUpResources(XMLResource res, Collection col) {
        if (res != null) {
            try {
                ((EXistResource) res).freeResources();
            } catch (XMLDBException ex) {
                logger.log(Level.SEVERE, "Exception occured during freeing resources", ex);
            }
        }
        if (col != null) {
            try {
                col.close();
            } catch (XMLDBException ex) {
                logger.log(Level.SEVERE, "Exception occured during closing connection", ex);
            }
        }
    }
}
